package client;

import java.nio.ByteBuffer;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
import shared.util.Pair;

/**
 * This class stores the incoming and outgoing packets for this client. 
 * They can be retrieved later for processing.
 *
 * @author Vincent Tsuei
 * @version 2012-11-17
 */
public class ClientIOStore {

    private static final int INITIAL_WRITE_BUFFER_SIZE = 4096;
    private final Client client;
    
    // The queues that will store the actual packets and byte arrays.
    private final ArrayBlockingQueue<ByteBuffer> readQueue = new ArrayBlockingQueue<ByteBuffer>(100);
    private final ArrayBlockingQueue<Pair<byte[], Boolean>> writeQueue = new ArrayBlockingQueue<Pair<byte[], Boolean>>(100);
    
    public ClientIOStore(Client client) {
        this.client = client;
    }
    
    /**
     * Add a packet to be processed. If queue is full, will block until there is room.
     *
     * Blocks if the queue is full.
     */
    public void queueInputPacket(ByteBuffer packet) {
        if (packet == null) {
            throw new RuntimeException("Trying to add a null ByteBuffer to input queue.");
        }
        try {
            readQueue.put(packet);
        } catch (InterruptedException ex) {
            //Should never be interrupted.
            Logger.getLogger(ClientIOStore.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    /**
     * Add a packet to be written out. If queue is full, will block until there is room.
     */
    public void queueOutputPacketAsync(byte[] b) {
        queueOutputPacket(b, false);
    }
    
    /**
     * Add a packet to be written out.
     * The object [notify] will be notified when write is done.
     * If queue is full, will block until there is room.
     */
    public void queueOutputPacketSync(byte[] b) {
        queueOutputPacket(b, true);
    }
    
    /**
     * Actually handle the queueing here.
     * if notify is true, we will notify the byte packet b when the packet is sent.
     */
    private void queueOutputPacket(byte[] b, boolean notify) {
        if (b == null) {
            throw new RuntimeException("Trying to add a null byte[] to output queue.");
        }
        try {
            writeQueue.put(new Pair<byte[], Boolean>(b, notify));
        } catch (InterruptedException ex) {
            //Should never be interrupted.
            Logger.getLogger(ClientIOStore.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    /**
     * Get the next packet to be written to server. Blocks until there is one available.
     */
    public Pair<byte[], Boolean> getNextOutputPacket() throws InterruptedException {
        return writeQueue.take();
    }
    
    /**
     * Get the next packet to be processed by the client. Blocks until there is one available.
     */
    public ByteBuffer getNextInputPacket() throws InterruptedException {
        return readQueue.take();
    }
}
